package de.codecrafters.tableview;

import de.codecrafters.tableview.model.TableColumnModel;
import de.codecrafters.tableview.model.TableColumnWeightModel;
import de.codecrafters.tableview.providers.TableDataRowBackgroundProvider;
import de.codecrafters.tableview.util.LogUtil;
import ohos.agp.components.*;
import ohos.agp.utils.LayoutAlignment;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * The abstract implementation of an adapter used to bring data to a {@link TableView}.
 *
 * @author ISchwarz
 */
public abstract class TableDataAdapter<T> extends BaseItemProvider {

    private static final String LOG_TAG = TableDataAdapter.class.getName();
    private final List<T> data;
    private TableColumnModel columnModel;
    private Context context;
    private TableDataRowBackgroundProvider<? super T> rowBackgroundProvider;


    /**
     * Creates a new TableDataAdapter.
     *
     * @param context The context that shall be used.
     * @param data    The data that shall be displayed.
     */
    public TableDataAdapter(final Context context, final T[] data) {
        this(context, 0, new ArrayList<>(Arrays.asList(data)));
    }

    /**
     * Creates a new TableDataAdapter.
     *
     * @param context The context that shall be used.
     * @param data    The data that shall be displayed.
     */
    public TableDataAdapter(final Context context, final List<T> data) {
        this(context, 0, data);
    }

    /**
     * Creates a new TableDataAdapter. (internally used)
     *
     * @param context     The context that shall be used.
     * @param columnCount The number of columns.
     * @param data        The data which shall be displayed in the table.
     */
    protected TableDataAdapter(final Context context, final int columnCount, final List<T> data) {
        this(context, new TableColumnWeightModel(columnCount), data);
    }

    /**
     * Creates a new TableDataAdapter. (internally used)
     *
     * @param context     The context that shall be used.
     * @param columnModel The column model to be used.
     * @param data        The data which shall be displayed in the table.
     */
    protected TableDataAdapter(final Context context, final TableColumnModel columnModel, final List<T> data) {
        super();
        this.columnModel = columnModel;
        this.data = data;
        this.context = context;
    }

    /**
     * Gives the data object that shall be displayed in the row with the given index.
     *
     * @param rowIndex The index of the row to get the data for.
     * @return The data that shall be displayed in the row with the given index.
     */
    public T getRowData(final int rowIndex) {
        return (T) getItem(rowIndex);
    }

    /**
     * Gives the data that is set to this adapter.
     *
     * @return The data this adapter is currently working with.
     */
    public List<T> getData() {
        return data;
    }

    /**
     * Gives the {@link Context} of this adapter. (Hint: use this method in the {@code getHeaderView()}-method
     * to programmatically initialize new views.)
     *
     * @return The {@link Context} of this adapter.
     */
    public Context getContext() {
        return context;
    }

    /**
     * Gives the {@link LayoutScatter} of this adapter. (Hint: use this method in the
     * {@code getHeaderView()}-method to inflate xml-layout-files.)
     *
     * @return The {@link LayoutScatter} of the context of this adapter.
     */
    public LayoutScatter getLayoutInflater() {
        return LayoutScatter.getInstance(getContext());
    }

    /**
     * Gives the {@link ResourceManager} of this adapter. (Hint: use this method in the
     * {@code getCellView()}-method to resolve resources.)
     *
     * @return The {@link ResourceManager} of the context of this adapter.
     */
    public ResourceManager getResources() {
        return getContext().getResourceManager();
    }

    /**
     * Method that gives the cell views for the different table cells.
     *
     * @param rowIndex    The index of the row to return the table cell view.
     * @param columnIndex The index of the column to return the table cell view.
     * @param parentView  The view to which the returned view will be added.
     * @return The created header view for the given column.
     * @throws IOException
     * @throws WrongTypeException
     * @throws NotExistException
     */
    public abstract Component getCellView(int rowIndex, int columnIndex, ComponentContainer parentView) throws NotExistException, WrongTypeException, IOException;

    @Override
    public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
        final DirectionalLayout rowView = new DirectionalLayout(getContext());
        rowView.setOrientation(DirectionalLayout.HORIZONTAL);
        final ComponentContainer.LayoutConfig rowLayoutParams = new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT);
        rowView.setLayoutConfig(rowLayoutParams);
        rowView.setAlignment(LayoutAlignment.VERTICAL_CENTER);

        Object rowData = null;
        try {
            rowData = getItem(i);
        } catch (final IndexOutOfBoundsException e) {
            HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00010, LOG_TAG), "No row date available for row with index " + i + ". " +
                    "Caught Exception: " + e.getMessage());
        }

        rowView.setBackground(rowBackgroundProvider.getRowBackground(i, rowData));

        final int tableWidth = componentContainer.getWidth();

        for (int columnIndex = 0; columnIndex < columnModel.getColumnCount(); columnIndex++) {
            Component cellView = null;
            try {
                cellView = getCellView(i, columnIndex, rowView);
            } catch (NotExistException e) {
                LogUtil.error(e.getMessage());
            } catch (WrongTypeException e) {
                LogUtil.error(e.getMessage());
            } catch (IOException e) {
                LogUtil.error(e.getMessage());
            }
            if (cellView == null) {
                cellView = new Text(getContext());
            }

            final int cellWidth = columnModel.getColumnWidth(columnIndex, tableWidth);
            final ComponentContainer.LayoutConfig cellLayoutParams = new ComponentContainer.LayoutConfig(cellWidth, DirectionalLayout.LayoutConfig.MATCH_CONTENT);
            cellView.setLayoutConfig(cellLayoutParams);
            rowView.addComponent(cellView);
        }

        return rowView;
    }

    /**
     * Sets the {@link TableDataRowBackgroundProvider} that will be used to define the table data rows background.
     *
     * @param rowBackgroundProvider The {@link TableDataRowBackgroundProvider} that shall be used.
     */
    protected void setRowBackgroundProvider(final TableDataRowBackgroundProvider<? super T> rowBackgroundProvider) {
        this.rowBackgroundProvider = rowBackgroundProvider;
    }

    /**
     * Gives the {@link TableColumnWeightModel} that is currently used to render the table headers.
     *
     * @return The {@link TableColumnModel} which is currently used..
     */
    protected TableColumnModel getColumnModel() {
        return columnModel;
    }

    /**
     * Sets the {@link TableColumnModel} that will be used to render the table cells.
     *
     * @param columnModel The {@link TableColumnModel} that should be set.
     */
    protected void setColumnModel(final TableColumnModel columnModel) {
        this.columnModel = columnModel;
    }
}
